#pragma once

class VertexObj
{
public:
	VertexObj() {}
	VertexObj(
		const DirectX::XMFLOAT3& p,
		const DirectX::XMFLOAT3& n,
		const DirectX::XMFLOAT3& t,
		const DirectX::XMFLOAT2& uv) :
		m_Position(p),
		m_Normal(n),
		m_TangentU(t),
		m_TexC(uv) {}
	VertexObj(
		float px, float py, float pz,
		float nx, float ny, float nz,
		float tx, float ty, float tz,
		float u, float v) :
		m_Position(px, py, pz),
		m_Normal(nx, ny, nz),
		m_TangentU(tx, ty, tz),
		m_TexC(u, v) {}

	DirectX::XMFLOAT3 m_Position;
	DirectX::XMFLOAT3 m_Normal;
	DirectX::XMFLOAT2 m_TexC;
	DirectX::XMFLOAT3 m_TangentU;

	static std::vector<D3D12_INPUT_ELEMENT_DESC> GetInputDesc()
	{
		std::vector<D3D12_INPUT_ELEMENT_DESC>  ret = 
		{
			{ "POSITION",	0, DXGI_FORMAT_R32G32B32_FLOAT, 0,	0,	D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
			{ "NORMAL",		0, DXGI_FORMAT_R32G32B32_FLOAT, 0,	12, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
			{ "TEXCOORD",	0, DXGI_FORMAT_R32G32_FLOAT, 0,		24, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
			{ "TANGENT",	0, DXGI_FORMAT_R32G32B32_FLOAT, 0,	32, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
		};
		return ret;
 
	}

	static string GetTypeName()
	{
		return "VertexObj";
	}
};

template<class TypeVertex = VertexObj, class TypeInx = std::uint32_t>
class MeshObj :public NameObjBase
{
public:
	MeshObj(string strName = "") :NameObjBase(strName) {};

	~MeshObj() {};
	inline void ClearPnt()
	{
		m_vPnt.clear();
	}

	inline void ClearInx()
	{
		m_vInx.clear();
	}

	inline const vector<TypeVertex>& GetPnt() const
	{
		return m_vPnt;
	}

	inline const vector<TypeInx>& GetInx() const
	{
		return m_vInx;
	}

	inline uint32_t GetPntSize() const
	{
		return m_vPnt.size();
	}

	inline uint32_t GetTriSize()
	{
		return m_vInx.size()/3;
	}

	inline void AddPnt(TypeVertex* pntData, int iCount = 1)
	{
		m_vPnt.insert(m_vPnt.end(), pntData, pntData + iCount);
	}

	inline void AddInx(TypeVertex* pntData, int iCount = 1)
	{
		m_vInx.insert(m_vInx.end(), pntData, pntData + iCount);
	}

	void SetPntData(const vector<TypeVertex>& vPnt)
	{
		m_vPnt = vPnt;
	}

	void SetInx(const vector<TypeInx>& vInx)
	{
		m_vInx = vInx;
	}
protected:
	vector<TypeVertex> m_vPnt;
	vector<TypeInx> m_vInx;
};

template<class TypeVertex = VertexObj, class TypeInx = std::uint32_t>
class MeshObjGeometry :public NameObjBase, public VeObjManage<pair<string, shared_ptr<MeshObj<TypeVertex, TypeInx>>>>
{
	typedef typename shared_ptr<MeshObj<TypeVertex, TypeInx>> PMeshObj;
public:
	struct suSubmeshGeometry
	{
		uint32_t m_inxSize = 0;
		uint32_t m_inxStart = 0;
		uint32_t m_inxPntStart = 0;
	};
public:
	MeshObjGeometry(PD3D12Device device = nullptr, PCommandList pCommandList = nullptr, string strName="") :NameObjBase(strName)
	{
		m_pDevice = device;
		m_pCommandList = pCommandList;
	}

	D3D12_VERTEX_BUFFER_VIEW VertexBufferView()const
	{
		D3D12_VERTEX_BUFFER_VIEW vbv;
		vbv.BufferLocation = m_vertexBufferGPU->GetGPUVirtualAddress();
		vbv.StrideInBytes = m_vertexByteStride;
		vbv.SizeInBytes = m_vertexBufferByteSize;

		return vbv;
	}

	D3D12_INDEX_BUFFER_VIEW IndexBufferView()const
	{
		D3D12_INDEX_BUFFER_VIEW ibv;
		ibv.BufferLocation = m_indexBufferGPU->GetGPUVirtualAddress();
		ibv.Format = m_indexFormat;
		ibv.SizeInBytes = m_indexBufferByteSize;

		return ibv;
	}

	void SetMesh(vector<string> vMeshName)
	{
		for (string sMeshName : vMeshName)
		{
			Add(make_pair(sMeshName, StandMeshManage::GetInstance()->Get(sMeshName)));
		}
		UpdateBuffer();
	}

	void UpdateBuffer()
	{
		ClearBuffer();
		if (!m_vObj.empty())
		{
			suSubmeshGeometry temp;
			temp.m_inxSize = m_vObj[0].second->GetTriSize() * 3;
			m_drawSubMesh[m_vObj[0].first] = temp;
			for (int i = 1; i < m_vObj.size(); ++i)
			{
				suSubmeshGeometry subGem = temp;
				PMeshObj pMeshObj = m_vObj[i - 1].second;

				subGem.m_inxPntStart += pMeshObj->GetPntSize();
				subGem.m_inxStart += pMeshObj->GetTriSize() * 3;
				subGem.m_inxSize = m_vObj[i].second->GetTriSize() * 3;
				m_drawSubMesh[m_vObj[i].first] = temp;
			}

			vector<TypeVertex> vPnt;
			vector<TypeInx> vInx;
			for (pair<string, PMeshObj>& paMesh : m_vObj)
			{
				vector<TypeVertex> vPntTemp = paMesh.second->GetPnt();
				vPnt.insert(vPnt.end(), vPntTemp.begin(), vPntTemp.end());

				vector<TypeInx> vInxTemp = paMesh.second->GetInx();
				vInx.insert(vInx.end(), vInxTemp.begin(), vInxTemp.end());
			}
			const UINT vbByteSize = (UINT)vPnt.size() * sizeof(TypeVertex);
			const UINT ibByteSize = (UINT)vInx.size() * sizeof(TypeInx);


			ThrowIfFailed(D3DCreateBlob(vbByteSize, &m_vertexBufferCPU));
			CopyMemory(m_vertexBufferCPU->GetBufferPointer(), vPnt.data(), vbByteSize);

			ThrowIfFailed(D3DCreateBlob(ibByteSize, &m_indexBufferCPU));
			CopyMemory(m_indexBufferCPU->GetBufferPointer(), vInx.data(), ibByteSize);

			m_vertexBufferGPU = d3dUtil::CreateDefaultBuffer(m_pDevice.Get(),
				m_pCommandList.Get(), vPnt.data(), vbByteSize, m_vertexBufferUploader);

			m_indexBufferGPU = d3dUtil::CreateDefaultBuffer(m_pDevice.Get(),
				m_pCommandList.Get(), vInx.data(), ibByteSize, m_indexBufferUploader);

			m_vertexByteStride = sizeof(TypeVertex);
			m_vertexBufferByteSize = vbByteSize;
			m_indexFormat = GetInxFormate();
			m_indexBufferByteSize = ibByteSize;
		}

	}

	vector<string> GetSubMeshName() 
	{
		vector<string> ret;
		for (std::unordered_map<std::string, suSubmeshGeometry>::iterator pos = m_drawSubMesh.begin(); pos != m_drawSubMesh.end(); ++pos)
		{
			ret.push_back(pos->first);
		}
		return ret;
	}

	suSubmeshGeometry GetSubMeshInfo(string strName)
	{
		return m_drawSubMesh[strName];
	}
protected:
	void ClearBuffer()
	{
		m_drawSubMesh.clear();
		m_vertexBufferCPU.Reset();
		m_indexBufferCPU.Reset();

		m_vertexBufferGPU.Reset();
		m_indexBufferGPU.Reset();


		m_vertexBufferUploader.Reset();
		m_indexBufferUploader.Reset();
	}

	DXGI_FORMAT GetInxFormate()
	{
		DXGI_FORMAT ret = DXGI_FORMAT_R32_UINT;
		int iSize = sizeof(TypeInx);
		if (iSize == 32) ret = DXGI_FORMAT_R32_UINT;
		if (iSize == 16) ret = DXGI_FORMAT_R16_UINT;
		if (iSize == 8) ret = DXGI_FORMAT_R8_UINT;
		return ret;
	}
protected:
	PD3D12Device m_pDevice = nullptr;

	PCommandList m_pCommandList = nullptr;
	// Data about the buffers.
	UINT m_vertexByteStride = 0;
	UINT m_vertexBufferByteSize = 0;
	DXGI_FORMAT m_indexFormat = DXGI_FORMAT_R16_UINT;
	UINT m_indexBufferByteSize = 0;

	std::unordered_map<std::string, suSubmeshGeometry> m_drawSubMesh;
	PD3DBlob m_vertexBufferCPU = nullptr;
	PD3DBlob m_indexBufferCPU = nullptr;

	PD3D12Resource m_vertexBufferGPU = nullptr;
	PD3D12Resource m_indexBufferGPU = nullptr;

	PD3D12Resource m_vertexBufferUploader = nullptr;
	PD3D12Resource m_indexBufferUploader = nullptr;
};

typedef MeshObj<VertexObj, std::uint32_t> StandMesh;

typedef MeshObjGeometry<VertexObj, std::uint32_t> StandGeometry;

class VerObjManage:public InstanceObj<VerObjManage>, public SortObjManage<string, vector<D3D12_INPUT_ELEMENT_DESC>>
{
public:
	VerObjManage();
	~VerObjManage();
}; 

class StandMeshManage :public InstanceObj<StandMeshManage>, public SortObjManage<string, shared_ptr<StandMesh>>
{
public:
	void InitMesh()
	{
		vector<VertexObj> v(24);

		float w2 = 0.5f*10;
		float h2 = 0.5f*10;
		float d2 = 0.5f*10;

		// Fill in the front face VertexObj data.
		v[0] = VertexObj(-w2, -h2, -d2, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f);
		v[1] = VertexObj(-w2, +h2, -d2, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f);
		v[2] = VertexObj(+w2, +h2, -d2, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f);
		v[3] = VertexObj(+w2, -h2, -d2, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f);

		// Fill in the back face VertexObj data.
		v[4] = VertexObj(-w2, -h2, +d2, 0.0f, 0.0f, 1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 1.0f);
		v[5] = VertexObj(+w2, -h2, +d2, 0.0f, 0.0f, 1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f);
		v[6] = VertexObj(+w2, +h2, +d2, 0.0f, 0.0f, 1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f);
		v[7] = VertexObj(-w2, +h2, +d2, 0.0f, 0.0f, 1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f);

		// Fill in the top face VertexObj data.
		v[8] = VertexObj(-w2, +h2, -d2, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f);
		v[9] = VertexObj(-w2, +h2, +d2, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f);
		v[10] = VertexObj(+w2, +h2, +d2, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f);
		v[11] = VertexObj(+w2, +h2, -d2, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f);

		// Fill in the bottom face VertexObj data.
		v[12] = VertexObj(-w2, -h2, -d2, 0.0f, -1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f, 1.0f);
		v[13] = VertexObj(+w2, -h2, -d2, 0.0f, -1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f);
		v[14] = VertexObj(+w2, -h2, +d2, 0.0f, -1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f);
		v[15] = VertexObj(-w2, -h2, +d2, 0.0f, -1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f);

		// Fill in the left face VertexObj data.
		v[16] = VertexObj(-w2, -h2, +d2, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f);
		v[17] = VertexObj(-w2, +h2, +d2, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f);
		v[18] = VertexObj(-w2, +h2, -d2, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f);
		v[19] = VertexObj(-w2, -h2, -d2, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f);

		// Fill in the right face VertexObj data.
		v[20] = VertexObj(+w2, -h2, -d2, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f);
		v[21] = VertexObj(+w2, +h2, -d2, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f);
		v[22] = VertexObj(+w2, +h2, +d2, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f);
		v[23] = VertexObj(+w2, -h2, +d2, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f);

		StandMesh meshData("Box");
		meshData.SetPntData(v);

		//
		// Create the indices.
		//

		vector<uint32_t> i(36);

		// Fill in the front face index data
		i[0] = 0; i[1] = 1; i[2] = 2;
		i[3] = 0; i[4] = 2; i[5] = 3;

		// Fill in the back face index data
		i[6] = 4; i[7] = 5; i[8] = 6;
		i[9] = 4; i[10] = 6; i[11] = 7;

		// Fill in the top face index data
		i[12] = 8; i[13] = 9; i[14] = 10;
		i[15] = 8; i[16] = 10; i[17] = 11;

		// Fill in the bottom face index data
		i[18] = 12; i[19] = 13; i[20] = 14;
		i[21] = 12; i[22] = 14; i[23] = 15;

		// Fill in the left face index data
		i[24] = 16; i[25] = 17; i[26] = 18;
		i[27] = 16; i[28] = 18; i[29] = 19;

		// Fill in the right face index data
		i[30] = 20; i[31] = 21; i[32] = 22;
		i[33] = 20; i[34] = 22; i[35] = 23;

		meshData.SetInx(i);

		shared_ptr<StandMesh> pMesh = shared_ptr<StandMesh>(new StandMesh());
		*pMesh = meshData;
		Add(meshData.GetName(), pMesh);
	}
};



